#include <stdio.h>
#include <kewinternal.h>
#include "myglib.h"

#define SOURCE_FILE_NAME "List"

#define list_value(OBJECT) ((GList *)((OBJECT)->context[0]))
#define check_list(VAL) KewCheckType((VAL), &KewListType, "list")

METHOD(list_addLast)
{
  GList *copy = g_list_copy(list_value(receiver));
  copy = g_list_append(copy, args[0]);
  results[0] = KewWrapList(copy);
}

METHOD(list_addFirst)
{
  GList *copy = g_list_copy(list_value(receiver));
  copy = g_list_prepend(copy, args[0]);
  results[0] = KewWrapList(copy);
}

METHOD(list_comma)
{
  GList *rhs_copy;
  GList *lhs_copy = g_list_copy(list_value(receiver));
  check_list(args[0]);
  rhs_copy = g_list_copy(list_value(args[0]));
  results[0] = KewWrapList(g_list_concat(lhs_copy, rhs_copy));
}

METHOD(list_size)
{
  results[0] = KewNewInt(g_list_length(list_value(receiver)));
}

METHOD(list_at)
{
  KewObject *element;
  /* FIXME: export int type so we can check it */
  element = (KewObject *) g_list_nth_data(list_value(receiver),
                                                     KewIntVal(args[0]));
  if (element != NULL) {
    results[0] = element;
    return;
  }

  results[0] = KewSend1(KewMissingKey, "raise", args[0]);
}

METHOD(list_combine) 
{
  GList *list1;
  GList *list2;
  GList *result = NULL;
  unsigned index = 1;

  check_list(args[0]);
  list1 = list_value(receiver);
  list2 = list_value(args[0]);

  while (list1 && list2) {
    KewObject *new_result = KewSend3(args[1], "do",
                                     KewNewInt(index),
                                     list1->data,
                                     list2->data);
    result = g_list_append(result, new_result);

    list1 = g_list_next(list1);
    list2 = g_list_next(list2);
    index++;
  }
  /* FIXME: raise MissingKey exception for length mismatches */
  results[0] = KewWrapList(result);
}

METHOD(list_last)
{
  GList *last = g_list_last(list_value(receiver));
  results[0] = (KewObject*) last->data;
}

METHOD(list_first)
{
  GList *first = g_list_first(list_value(receiver));
  results[0] = (KewObject*) first->data;
}


METHOD(list_dropFirst)
{
  GList *copy = g_list_copy(list_value(receiver));
  GList *first = g_list_first(copy);
  copy = g_list_remove_link(copy, first);
  results[0] = KewWrapList(copy);
}

METHOD(list_dropLast)
{
  GList *copy = g_list_copy(list_value(receiver));
  GList *last = g_list_last(copy);
  copy = g_list_remove_link(copy, last);
  results[0] = KewWrapList(copy);
}

METHOD(list_list)
{
  results[0] = receiver;
}

METHOD(list_reader_read)
{
  results[0] = (KewObject *) list_value(receiver)->data;
  list_value(receiver) = list_value(receiver)->next;
}

METHOD(list_reader_empty)
{
  results[0] = KewNewBool(list_value(receiver) == NULL);
}

METHOD(list_reader_reader)
{
  results[0] = receiver;
}

static KewObjectType list_reader_type = {
  SOURCE_FILE_NAME, 0,
  1,
  {
    {"read", list_reader_read, 0, 1},
    {"empty", list_reader_empty, 0, 1},
    {"reader", list_reader_reader, 0, 1},
    {NULL, NULL, 0, 0}
  }
};

METHOD(list_reader)
{
  results[0] = KewNewObject(&list_reader_type);
  results[0]->context[0] = (KewObject *) list_value(receiver);
}

KewObjectType KewListType = {
  SOURCE_FILE_NAME, __LINE__,
  1,
  {
    {"hash", Kew__GenericHash, 0, 1},
    {"=", Kew__GenericEqual, 1, 1},
    {"&", list_addLast, 1, 1},
    {"addFirst", list_addFirst, 1, 1},
    {",", list_comma, 1, 1},
    {"size", list_size, 0, 1},
    {"at", list_at, 1, 1},
    {"combine", list_combine, 2, 1},
    {"last", list_last, 0, 1},
    {"first", list_first, 0, 1},
    {"dropFirst", list_dropFirst, 0, 1},
    {"dropLast", list_dropLast, 0, 1},
    {"list", list_list, 0, 1},
    {"reader", list_reader, 0, 1},
    {NULL, NULL, 0, 0}
  }
};

KewObject *
KewWrapList(GList *l)
{
  KewObject *result = KewNewObject(&KewListType);
  list_value(result) = l;
  return result;
}

KewObject *
KewNewList(GList *l)
{
  return KewWrapList(g_list_copy(l));
}

KewObject *
KewNewListFromArray(KewObject *array[],
                    unsigned array_size)
{
  GList *list = NULL;
  while (array_size > 0)
  {
    list = g_list_prepend(list, array[array_size]);
    array_size--;
  }
  return KewWrapList(list);
}


void
Kew__InitList(void)
{
  KewList = KewWrapList(NULL);
}
